/**************************************************************
 * fetchlyric.c - interface to lyric search plugins
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is  free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
**************************************************************/


#include <sys/types.h>
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>

#include "common.h"
#include "fetchlyric.h"

struct plugin_item{
  struct fetchlyric_plugin_descrip *plugin_descrip;
  struct fetchlyric_plugin *plugin;
  void *handle;
};


/* 
 *plugins fetch lyric from local or nonlocal, if it fetches fast
 *enough, it can also be categorized as local
 */
struct __fetchlyric{
  GSList *plugin_list_local;
  GSList *plugin_list_nonlocal;

  gboolean initialized;
};


static gchar *next_in_dir(const gchar *dir)
{
  static const gchar *lastdir = NULL;
  static DIR *d = NULL;
  static struct dirent *ent = NULL;

  if(! dir)
	return NULL;

  if(dir != lastdir)  {
	if(lastdir)
	  closedir(d);

	lastdir = dir;
	d = opendir(lastdir);
	if(! d)
	  return NULL;
  }else if(! d)  {
	d = opendir(lastdir);
	if(! d)
	  return NULL;
  }

  ent = readdir(d);
  if(ent)
	return g_strdup_printf("%s/%s", lastdir, ent->d_name);

  closedir(d);
  d = NULL;
  return NULL;
}


static gint __find_plugin(gconstpointer a, gconstpointer b);

static gboolean __plugin_exists(fetchlyric_t *fl, const gchar *plugin)
{
  if(g_slist_find_custom(fl->plugin_list_local, plugin, __find_plugin) ||
	 g_slist_find_custom(fl->plugin_list_nonlocal, plugin, __find_plugin))
	return TRUE;

  return FALSE;
}


static gboolean __check_and_setup_plugin(fetchlyric_t *fl, const gchar *path, int argc, char **argv)
{
  struct fetchlyric_plugin_descrip *plugin_descrip;
  struct fetchlyric_plugin *plugin;
  struct plugin_item *plugin_item;
  void *handle;

  handle = dlopen(path, RTLD_LAZY);
  if(! handle) {
#ifndef NDEBUG
    fprintf(stderr, "** error loading %s:%s\n", path, dlerror());
#endif
    return FALSE;
  }

  plugin_descrip = (struct fetchlyric_plugin_descrip *)dlsym(handle, FETCHLYRIC_EXPORT_SYMBOL_NAME);

  if(! plugin_descrip)  {
#ifndef NDEBUG
    fprintf(stderr, "** Bad plugin:%s\n", path);
#endif
    dlclose(handle);
    return FALSE;
  }

  if(__plugin_exists(fl, plugin_descrip->name))  {
	dlclose(handle);
	return TRUE;
  }

  /* refuse to init plugins without @exit member */
  if(plugin_descrip->init && plugin_descrip->exit)  {
    plugin = (plugin_descrip->init)(argc, argv);
    if(! plugin)  {
#ifndef NDEBUG
      fprintf(stderr, "** Failed to init plugin:%s\n", path);
#endif
      dlclose(handle);
      return FALSE;
    }
  }else  {
#ifndef NDEBUG
    fprintf(stderr, "** No init function:%s\n", path);
#endif
    dlclose(handle);
    return FALSE;
  }

#ifndef NDEBUG
  fprintf(stderr, "loading %s successfully\n", path);
#endif

  plugin_item = g_new(struct plugin_item, 1);

  plugin_item->plugin_descrip = plugin_descrip;
  plugin_item->plugin = plugin;
  plugin_item->handle = handle;

  if(plugin_descrip->islocal)
    fl->plugin_list_local = g_slist_append(fl->plugin_list_local, plugin_item);
  else
    fl->plugin_list_nonlocal = g_slist_append(fl->plugin_list_nonlocal, plugin_item);

  return TRUE;
}


static gboolean load_all(fetchlyric_t *fl, const gchar *plugindir, int argc, char **argv)
{
  void *handle;
  DIR *dir;
  struct dirent *item;
  gchar *path;
  const  gchar *directory;

  if(plugindir)
    directory = plugindir;
  else
    directory = FETCHLYRIC_BASEDIR;

  dir = opendir(directory);
  if(! dir)  {
#ifndef NDEBUG
    fprintf(stderr, "** error open dir %s: %s\n", directory, strerror(errno));
#endif
    return FALSE;
  }

  while(item = readdir(dir))  {
	if(! strcmp(item->d_name, ".") || ! strcmp(item->d_name, "..") )
	  continue;
    path = g_strdup_printf("%s%s", directory, item->d_name);
    __check_and_setup_plugin(fl, path, argc, argv);
    g_free(path);
  }

  closedir(dir);

  fl->initialized = TRUE;

  if(fl->plugin_list_local || fl->plugin_list_nonlocal)
    return TRUE;
  else
    return FALSE;
}


static void unload_all(fetchlyric_t *fl)
{
  struct plugin_item *item;

  if(! fl->plugin_list_local && ! fl->plugin_list_nonlocal)
    return;

  while(fl->plugin_list_local)  {
    item = (struct plugin_item *)fl->plugin_list_local->data;
    (item->plugin_descrip->exit)(item->plugin);
    dlclose(item->handle);

    fl->plugin_list_local = g_slist_remove(fl->plugin_list_local, item);
    g_free(item);
  }

  while(fl->plugin_list_nonlocal)  {
    item = (struct plugin_item *)fl->plugin_list_nonlocal->data;
    (item->plugin_descrip->exit)(item->plugin);
    dlclose(item->handle);

    fl->plugin_list_nonlocal = g_slist_remove(fl->plugin_list_nonlocal, item);
    g_free(item);
  }

  fl->initialized = FALSE;
}



static gint __find_plugin(gconstpointer a, gconstpointer b)
{
  struct plugin_item *item = (struct plugin_item *)a;
  const gchar *plugin = (const gchar *)b;

  return g_strcmp0(item->plugin_descrip->name, plugin);
}


gboolean fetch_lyric_load_all(fetchlyric_t **fl, const char *plugindir, int argc, char **argv)
{
  if(! fl)
	return FALSE;

  if(! *fl)
	*fl = g_new0(fetchlyric_t, 1);

  if(load_all(*fl, plugindir, argc, argv))
	return TRUE;

  g_free(*fl);
  return FALSE;
}



gboolean fetch_lyric_load_plugin(fetchlyric_t **fl, const gchar *plugin, const gchar *plugindir, int argc, char **argv)
{
  const gchar *dir;
  const gchar *path;
  fetchlyric_t *tmp;
  DIR *d;
  struct dirent *ent;
  gboolean free_on_fail = FALSE, res;

  if(! plugin || ! fl)
    return FALSE;

  if(plugindir)
    dir = plugindir;
  else
    dir = FETCHLYRIC_BASEDIR;

  if(*fl)  {
	if(g_slist_find_custom((*fl)->plugin_list_local, plugin, __find_plugin) ||
	   g_slist_find_custom((*fl)->plugin_list_nonlocal, plugin, __find_plugin))
	  return TRUE;
  }else  {
	d = opendir(dir);
	if(! d)
	  return FALSE;
	*fl = g_new0(fetchlyric_t, 1);
	tmp = g_new0(fetchlyric_t, 1);
	free_on_fail = TRUE;
  }

  while(ent = readdir(d))  {
	path = g_strdup_printf("%s/%s", dir, ent->d_name);
	res = __check_and_setup_plugin(tmp, path, argc, argv);
	g_free((void *)path);
	if(res)
	  if(g_slist_find_custom(tmp->plugin_list_local, plugin, __find_plugin) ||
		 g_slist_find_custom(tmp->plugin_list_nonlocal, plugin, __find_plugin))  {
		(*fl)->plugin_list_local = g_slist_concat((*fl)->plugin_list_local, tmp->plugin_list_local);
		(*fl)->plugin_list_nonlocal = g_slist_concat((*fl)->plugin_list_nonlocal, tmp->plugin_list_nonlocal);
		g_free(tmp);
		return TRUE;
	  }
	unload_all(tmp);
  }

  closedir(d);
  g_free(tmp);
  if(free_on_fail)  {
	g_free(*fl);
	*fl = NULL;
  }
  return FALSE;
}


gboolean fetch_lyric_load_plugin_from_file(fetchlyric_t **fl, const gchar *path, int argc, char **argv)
{
  void *handle;
  struct fetchlyric_plugin_descrip *plugin_descrip;
  fetchlyric_t *tmp;

  if(! fl)
	return FALSE;

  handle = dlopen(path, RTLD_LAZY);
  if(! handle) {
#ifndef NDEBUG
    fprintf(stderr, "** error loading %s:%s\n", path, dlerror());
#endif
    return FALSE;
  }

  plugin_descrip = (struct fetchlyric_plugin_descrip *)dlsym(handle, FETCHLYRIC_EXPORT_SYMBOL_NAME);

  if(! plugin_descrip)  {
#ifndef NDEBUG
    fprintf(stderr, "** Bad plugin: %s: %s\n", path, dlerror());
#endif
    dlclose(handle);
    return FALSE;
  }

  tmp = g_new0(fetchlyric_t, 1);
  if( __check_and_setup_plugin(tmp, path, argc, argv))  {
	if(! *fl)
	  *fl = g_new0(fetchlyric_t, 1);
	(*fl)->plugin_list_local = g_slist_concat((*fl)->plugin_list_local, tmp->plugin_list_local);
	(*fl)->plugin_list_nonlocal = g_slist_concat((*fl)->plugin_list_nonlocal, tmp->plugin_list_nonlocal);
	g_free(tmp);
	return TRUE;
  }

  g_free(tmp);
  return FALSE;
}



struct fetchlyric_plugin_descrip *fetch_lyric_get_descrip(fetchlyric_t *fl, const gchar *plugin)
{
  GSList *elem;

  if(! plugin || ! fl)
    return NULL;

  elem = g_slist_find_custom(fl->plugin_list_local, plugin, __find_plugin);
  if(elem)
    return ((struct plugin_item *)elem->data)->plugin_descrip;

  elem = g_slist_find_custom(fl->plugin_list_nonlocal, plugin, __find_plugin);
  if(elem)
    return ((struct plugin_item *)elem->data)->plugin_descrip;

  return NULL;
}



struct fetchlyric_plugin         *fetch_lyric_get_plugin(fetchlyric_t *fl, const gchar *plugin)
{
  GSList *elem;

  if(! plugin || ! fl)
    return NULL;

  elem = g_slist_find_custom(fl->plugin_list_local, plugin, __find_plugin);
  if(elem)
    return ((struct plugin_item *)elem->data)->plugin;

  elem = g_slist_find_custom(fl->plugin_list_nonlocal, plugin, __find_plugin);
  if(elem)
    return ((struct plugin_item *)elem->data)->plugin;

  return NULL;
}



static FILE *__get_lyric(GSList *list, const gchar *title, const gchar *artist)
{
  FILE *lyric;
  struct plugin_item *plugin_item;
  GSList *elem = list;

  while(elem)  {
    plugin_item = (struct plugin_item *)elem->data;
    if(plugin_item->plugin->fetchlyric_get)
      lyric =  (plugin_item->plugin->fetchlyric_get)(plugin_item->plugin, title, artist);
    if(lyric)
      return lyric;
    elem = g_slist_next(elem);
  }

  return NULL;
}


FILE *fetch_lyric_file(fetchlyric_t *fl, const gchar *title, const gchar *artist)
{
  FILE *lyric;

  if(! fl || ! fl->initialized || !(fl->plugin_list_local && fl->plugin_list_nonlocal) ||  ! title)
    return NULL;

  lyric = __get_lyric(fl->plugin_list_local, title, artist);
  if(lyric)
    return lyric;
  return  __get_lyric(fl->plugin_list_nonlocal, title, artist);
}



FILE *fetch_lyric_file_from(fetchlyric_t *fl, const gchar *plugin, const gchar *title, const gchar *artist)
{
  FILE *lyric;
  GSList *elem;
  struct plugin_item *plugin_item;

  if(! fl || ! fl->initialized || !(fl->plugin_list_local && fl->plugin_list_nonlocal) || ! plugin ||! title)
    return NULL;
  
  elem = g_slist_find_custom(fl->plugin_list_local, plugin, __find_plugin);
  if(! elem)  {
    elem = g_slist_find_custom(fl->plugin_list_nonlocal, plugin, __find_plugin);
    if(! elem)
      return NULL;
  }

  plugin_item = (struct plugin_item *)elem->data;

  return plugin_item->plugin->fetchlyric_get(plugin_item->plugin, title, artist);
}


GSList *__get_list(GSList *list, const gchar *title, const gchar *artist)
{
  GSList *iter, *tmp;
  GSList *res = NULL;
  struct fetchlyric_plugin *plugin;

  if(! list)
    return NULL;


  iter = list;
  while(iter)  {
    plugin  = ((struct plugin_item *)iter->data)->plugin;
    if(plugin->fetchlyric_get_list && 
       plugin->fetchlyric_get_from_url &&
       plugin->fetchlyric_free_list)  {
	  tmp = (plugin->fetchlyric_get_list)(plugin, title, artist);
      res = g_slist_concat(res, tmp);
    }
    iter = g_slist_next(iter);
  }
}


GSList *fetch_lyric_get_list(fetchlyric_t *fl, const gchar *title, const gchar *artist)
{
  GSList *res;

  if(! fl || ! fl->initialized || !(fl->plugin_list_local && fl->plugin_list_nonlocal) || ! title)
    return NULL;

  res = __get_list(fl->plugin_list_local, title, artist);
  return g_slist_concat(res,  __get_list(fl->plugin_list_nonlocal, title, artist));
}



GSList *fetch_lyric_get_list_from(fetchlyric_t *fl, const gchar *plugin, const gchar *title, const gchar *artist)
{
  GSList *elem;
  struct fetchlyric_plugin *plugin_struct;

  if(! fl || ! fl->initialized || !(fl->plugin_list_local && fl->plugin_list_nonlocal) || !plugin || ! title)
    return NULL;

  elem = g_slist_find_custom(fl->plugin_list_local, plugin, __find_plugin);
  if(! elem)  {
    elem = g_slist_find_custom(fl->plugin_list_nonlocal, plugin, __find_plugin);
    if(! elem)
      return NULL;
  }

  plugin_struct = ((struct plugin_item *)(elem->data))->plugin;

  if(plugin_struct->fetchlyric_get_list && 
     plugin_struct->fetchlyric_get_from_url &&
     plugin_struct->fetchlyric_free_list)
    return (plugin_struct->fetchlyric_get_list)(plugin_struct, title, artist);
}


FILE   *fetch_lyric_file_next(GSList *list)
{
  struct lyric_url *url;
  static GSList *elem = NULL;
  static GSList *start = NULL;

  if(! list)  {
	start = NULL;
	elem = NULL;
	return NULL;
  }

  if(list)
    if(start != list)
      start = elem = list;

  if(! elem)
    return NULL;

  url = (struct lyric_url *)elem->data;
  elem = g_slist_next(elem);

  return (url->plugin->fetchlyric_get_from_url)(url->plugin, url);
}



FILE   *fetch_lyric_file_next_r(GSList *list, GSList **restore)
{
  struct lyric_url *url;

  if(! list && ! *restore)
    return NULL;

  if(list)
    *restore = list;

  url = (struct lyric_url *)((*restore)->data);
  *restore = g_slist_next(*restore);

  return (url->plugin->fetchlyric_get_from_url)(url->plugin, url);
}


gboolean fetch_lyric_free_list(GSList *list)
{
  GSList *left, *last;
  struct lyric_url *url;

  if(! list)
    return FALSE;

  left = list;

  while(left)  {
    url = (struct lyric_url *)left->data;
    last = left;
    left = (url->plugin->fetchlyric_free_list)(url->plugin, left);
    if(last == left)		/* this should never happen */
      return FALSE;
  }

  return TRUE;
}


gboolean fetch_lyric_unload_plugin(fetchlyric_t *fl, const gchar *plugin)
{
  GSList *found;
  struct plugin_item *plugin_item;

  if(! fl )
	if( ! plugin)
	  return FALSE;
	else
	  return TRUE;

  if(! fl->initialized || !(fl->plugin_list_local && fl->plugin_list_nonlocal))
	return TRUE;

  found = g_slist_find_custom(fl->plugin_list_local, plugin, __find_plugin);
  if(found)
    fl->plugin_list_local = g_slist_remove(fl->plugin_list_local, found->data);
  else  {
    found = g_slist_find_custom(fl->plugin_list_nonlocal, plugin, __find_plugin);
    if(found)
      fl->plugin_list_nonlocal = g_slist_remove(fl->plugin_list_nonlocal, found->data);
    else
      return TRUE;
  }

  plugin_item = (struct plugin_item *)found->data;
  if(plugin_item->plugin_descrip->exit)
    (plugin_item->plugin_descrip->exit)(plugin_item->plugin);
  dlclose(plugin_item->handle);
  g_free(plugin_item);

  return TRUE;
}



void fetch_lyric_unload_all(fetchlyric_t **fl)
{
  if(! fl || ! *fl)
	return;
  unload_all(*fl);
  g_free(*fl);
  *fl = NULL;
}


static gchar **__list_plugin(GSList *list)
{
  gchar **vec;
  gint len, i = 0;
  struct fetchlyric_plugin_descrip *plugin_descrip;

  if(! list)
	return NULL;
  vec = g_new(gchar *, 10);
  len = 9;
  do{
	plugin_descrip = ((struct plugin_item *)(list->data))->plugin_descrip;
	vec[i] = g_strdup(plugin_descrip->name);
	i++;
	if(i == len)  {
	  vec = g_renew(gchar *, vec, len + 10);
	  len += 10;
	}
  }while(list = g_slist_next(list));
  vec[i] = NULL;

  return vec;
}

gchar **fetch_lyric_plugin_list(fetchlyric_t *fl)
{
  gchar **local, **nonlocal;
  gchar **tmp, **ret;
  gint i = 0;

  if(! fl || ! (fl->plugin_list_local || fl->plugin_list_nonlocal))
	return NULL;

  local = __list_plugin(fl->plugin_list_local);
  nonlocal = __list_plugin(fl->plugin_list_nonlocal);

  if(! local)
	return nonlocal;
  if(! nonlocal)
	return local;

  for(tmp = local; *tmp; tmp++, i++);
  for(tmp = nonlocal; *tmp; tmp++, i++);
  ret = g_new0(gchar *, i + 1);
  i = 0;
  for(tmp = local; *tmp; *(ret + i++) = *tmp, tmp++);
  for(tmp = nonlocal; *tmp; *(ret + i++) = *tmp, tmp++);

  g_free(local);
  g_free(nonlocal);

  return ret;
}


gchar **plugin_list(const gchar *plugindir)
{
  void *handle;
  DIR *dir;
  struct fetchlyric_plugin_descrip *plugin_descrip;
  struct dirent *item;
  gchar *path;
  const gchar *directory;
  gchar **list;
  gint i = 0, length;

  if(plugindir)
    directory = plugindir;
  else
    directory = FETCHLYRIC_BASEDIR;

  dir = opendir(directory);
  if(! dir)  {
#ifndef NDEBUG
    fprintf(stderr, "** error open dir %s: %s\n", directory, strerror(errno));
#endif
    return NULL;
  }

  list = g_new(gchar *, 10);
  length = 9;

  while(item = readdir(dir))  {
    path = g_strdup_printf("%s/%s", directory, item->d_name);
    handle = dlopen(path, RTLD_LAZY);
    if(! handle) {
#ifndef NDEBUG
      fprintf(stderr, "** error loading %s:%s\n", path, dlerror());
#endif
      continue;
    }

    plugin_descrip = (struct fetchlyric_plugin_descrip *)dlsym(handle, FETCHLYRIC_EXPORT_SYMBOL_NAME);
    if(! plugin_descrip)  {
#ifndef NDEBUG
      fprintf(stderr, "** Bad plugin:%s\n", path);
#endif
      dlclose(handle);
      continue;
    }
    g_free(path);

/*     if(plugin_list_local) */
/*       if(g_slist_find_custom(plugin_list_local, plugin_descrip->name, __find_plugin))  { */
/* 		dlclose(handle); */
/* 		continue; */
/*       } */

/*     if(plugin_list_nonlocal) */
/*       if(g_slist_find_custom(plugin_list_nonlocal, plugin_descrip->name, __find_plugin))  { */
/* 		dlclose(handle); */
/* 		continue; */
/*       } */

    if(i == length)  {
      list = g_renew(gchar *, list, length + 10 + 1);
      length += 10;
    }

    *(list + i++) = g_strdup(plugin_descrip->name);     
    dlclose(handle);
  }

  closedir(dir);

  if(i == 0)  {
	g_free(list);
	return NULL;
  }else
  *(list + i) = NULL;

  return list;
}


/*************** test code *******************/
#if defined(TEST_FETCHLYRIC)

int main(int argc,char **argv)
{
  
  return 0;
}

#endif
